package conference.controller;
import java.lang.*;
import java.io.*;
import java.net.*;
import java.util.LinkedList;
import java.util.Observable;
import java.util.Observer;

import conference.TCPCommands;
import conference.model.Client;
import conference.model.ClientList;
import conference.model.ConferenceState;
import conference.model.ConferenceState.NOTIFICATION_TYPE;
import conference.model.ConferenceStateClientSide.CALL_STATE;
import conference.model.ConferenceStateClientSide;
import conference.view.Ring;


/**
* 
* a class handling the tcp connection to the server on the client side
* @author samsjo-8 
*/
public class ClientSideTCP implements Observer{
	
	ButtonHandling buttonHandling;
	//static String computerNearWindow ="130.240.63.34";
	String localHost = "localhost";
	static int port = ConferenceState.TCP_PORT;
	private Thread output = null;
	private Thread input = null;
	OutputList sendingList = new OutputList();
	
	static Socket server = null;
	
	/* obtain an output stream to the server... */
    static PrintWriter out;
    /* ... and an input stream */
    static BufferedReader in;
    ConferenceStateClientSide conference;
    /* stdin stream */
    BufferedReader stdIn = new BufferedReader(
            new InputStreamReader(System.in));
	
	/**
   * constructor
	 * @param buttonHandling the owner class, buttonhandling
    @param conference the conference state (client side version)
	 * credit: followed example at http://pguides.net/java/tcp-client-server-chat
	 * @throws IOException 
	 * @throws InterruptedException 
	 */
	
	public ClientSideTCP(ConferenceStateClientSide conference, ButtonHandling buttonHandling) throws IOException, InterruptedException{
		this.conference=conference;
		this.buttonHandling = buttonHandling;
		conference.addObserver(this);

        try {
            /* try to open a socket to the server at the given host:port */
            server = new Socket(conference.getServerIp(), port);
        } catch (UnknownHostException e) {
        	System.out.println("Cannot connect to server. Please make sure that IP is correct and that the the server is running.");
            //System.err.println(e);
            return;
        } catch (ConnectException e) {
        	System.out.println("Cannot connect to server. Please make sure that IP is correct and that the the server is running.");
        	return;
        }
        
        in = new BufferedReader(new InputStreamReader(
                server.getInputStream()));
		 out = new PrintWriter(server.getOutputStream(), true);
        
        System.out.println("tjo!");
        System.out.println(in.readLine());//read server response
        boolean nickInUse = true;
        for(int i=0;nickInUse&&i<10;i++){
        	if(i==0){
        		out.println(conference.getThisClient().getNick());
        	} else {
        		out.println(conference.getThisClient().getNick()+i);
        	}
        	String response = in.readLine();//read the reply (OK or NICK_IN_USE)
        	if(response.equals(TCPCommands.server_msg_NICK_IN_USE)){
        		//try again with a different nick
        	} else if (response.equals(TCPCommands.server_msg_OK)){
        		break; //the wanted response
        	} else {
        		//unexpected message
        		System.out.println("unexpected message from server: "+response);
        		return;
        	}
        }
        //TODO: handle
        System.out.println("user has entered conference");
		conference.setEnteredConference();
        
        //request full list from server
		send(TCPCommands.client_msg_REQUESTING_FULL_CLIENT_LIST);
		
		//ask who is the current broadcaster
        send(TCPCommands.client_msg_REQUESTING_BROADCAST_INFO);
		
		//create the two threads
        System.out.println("before thread exec.");
				input= new Thread(new Input(server)); //thread that listens to server messages
				input.start();
				output = new Thread(new Output(server)); //thread that sends messages to the server
				output.start();
		System.out.println("after thread exec.");
	}
	
	/**
	 * the class observes ConferenceStateClientSide for changes, and gives the server notification of these changes
	 */
	@Override
	public void update(Observable object, Object notType) {
		System.out.println("gets here!!");
		//do action depending on update type
		
		if(notType==NOTIFICATION_TYPE.PRIVATE_TALK_UPDATE){
				if(conference.callState==CALL_STATE.INVITING){
					System.out.println("before send");
					send(TCPCommands.SIP_msg_INVITE+" "+conference.getPrivateTalkClient().toTransferString());
					System.out.println("after send");
				}
		}
		else if(notType==NOTIFICATION_TYPE.BROADCASTER_UPDATE){
			System.out.println("received broadcast update");
			//if(conference.getCurrentBroadCaster().equals(conference.getThisClient())){
				//send(TCPCommands.client_msg_WANTS_TO_BROADCAST);
			//}
		}
		else if(notType==NOTIFICATION_TYPE.CLIENT_LIST_UPDATE){
			//do nothing
		}
		else if(notType==NOTIFICATION_TYPE.CONFERENCE_PARTICIPATION_UPDATE){
			if(conference.hasEnteredConference==false){
				send(TCPCommands.client_msg_LEAVING);
				this.buttonHandling.disconnect();
			}
		}
	}
	

/**
*
* thread listening for messages from the server
*/
	class Input implements Runnable {
	    private BufferedReader in = null;

	    public Input(Socket server) throws IOException {
	        /* obtain an input stream from the server */
	        in = new BufferedReader(new InputStreamReader(
	                    server.getInputStream()));
	        //this.run();
	        System.out.println("InputThread started.");
	    }

	    public void run() {
	        String msg;
	        try {
	            /* loop reading messages from the server and show them 
	             * on stdout */
	            while ((msg = in.readLine()) != null) {
	                System.out.println("message from server: "+msg);
	                handleMessage(msg);
	            }
	        } catch (IOException e) {
	            System.err.println(e);
	        }
	    }
	    
	}
	
	
	
	/**
	 * this is a thread that handles all output messages (except for some initial messages in the beginning)
	 * @author samsjo-8
	 *
	 */
	class Output implements Runnable {
	    private BufferedReader in = null;

	    public Output(Socket server) throws IOException {
	        /* obtain an input stream from the server */
	        in = new BufferedReader(new InputStreamReader(
	                    server.getInputStream()));
	        System.out.println("OutputThread started.");
	    }

	    public void run() {
	        String msg;
	        try {
	            /* loop reading messages from the server and show them 
	             * on stdout */
	            while (true) {
	            	//System.out.println("looping");
	                //if list is empty
	            	if(sendingList.isEmpty()){
	            		try {
	            			Thread.sleep(200);
						} catch (InterruptedException e) {
							e.printStackTrace();
							break;
						}
	            	} else {
	            		msg = sendingList.pickNext();
		            	sendNow(msg);
	            	}
	            }
	            
	        } catch (Exception e) {
	            System.err.println(e);
	            System.out.println("Output thread died.");
	        }
	    }
	}
	
	/**
	 * To this list, messages that are to be sent  to the server are added.
	 * @author samsjo-8
	 *
	 */
	private class OutputList{
		
		//ClientSideTCP owner
		LinkedList<String> list = new LinkedList<String>();
		OutputList(){
			super();
		}
		
		//can only be called from within the controller package
		void addMsg(String msg){
			list.add(msg);
			if(output!=null){
				//wake up thread: out
			}
		}
		
		boolean isEmpty(){
			return list.isEmpty();
			
		}
		
		String pickNext(){
			return list.pollFirst(); //returns the msg that has waited the longest
		}
		
		
	}


/**
*
* directly transmits a message to the server. only to be called by the output stream (in the normal case)
* 
*/
	void sendNow(String msg) {
		out.println(msg);
		System.out.println("Sent: "+msg);
	}
	
 
/**
* 
* this method is called, handling all messages coming from the server
*/  
	public void handleMessage(String msg) {
		int index = msg.indexOf(' ');
		if (index > -1) {
			String firstword = msg.substring(0, index);
			String ip;
			String name;
			if (firstword.equals(TCPCommands.server_msg_USER_LEFT)) {
				ip = msg.substring(index+1, msg.indexOf(':'));
				System.out.println("removing client with ip "+ip+" from the list.");
				conference.getList().remove(conference.getList().getClientFromIp(ip));
			} else if (firstword.equals(TCPCommands.server_msg_USER_JOINED)) {
				ip = msg.substring(index+1, msg.indexOf(':'));
				name = msg.substring(msg.indexOf(':')+1);
				System.out.println("adding client "+name+" with ip "+ip+" to the list.");
				conference.getList().add(new Client(ip, name));
			
				//
			}  else if (firstword.equals(TCPCommands.server_msg_INCOMING_FULL_CLIENT_LIST)) {
				ClientList cl = new ClientList(conference);
				String EndOfString = msg.substring(index+1);
				while(EndOfString.indexOf(' ')>-1){
					ip = EndOfString.substring(0, EndOfString.indexOf(':'));
					name = EndOfString.substring(EndOfString.indexOf(':')+1,EndOfString.indexOf(' '));
					cl.add(new Client(ip, name));
					EndOfString = EndOfString.substring(EndOfString.indexOf(' ')+1);
				}
//				ip = EndOfString.substring(0, EndOfString.indexOf(':'));
//				name = EndOfString.substring(EndOfString.indexOf(':')+1);
//				cl.add(new Client(ip, name));
				conference.setList(cl);
			} 
			/////broadcasting/////
			
			else if (firstword.equals(TCPCommands.server_msg_NEW_BROADCASTER)) {
					ip = msg.substring(index+1, msg.indexOf(':'));
					name = msg.substring(msg.indexOf(':')+1);
					Client newBroadcaster = new Client(ip, name);
					conference.setBroadCaster(newBroadcaster);
				} 
			else if (firstword.equals(TCPCommands.server_msg_NO_BROADCASTER)) {
					conference.setBroadCaster(null);
				}
			
			
			
			/******private chat-related messages *******/
			
			else if (firstword.equals(TCPCommands.SIP_msg_INVITE)) {
				
				ip = msg.substring(index+1, msg.indexOf(':'));
				name = msg.substring(msg.indexOf(':')+1);
				
				//Client oldClient = conference.getPrivateTalkClient();
				if(conference.getCallState()==CALL_STATE.INVITING || 
						conference.getCallState()==CALL_STATE.IN_CALL){
					//send busy to the calling client if we are already calling or talking to someone
							//if(oldClient!=null){
					Client newClient = new Client(ip, name);
					send(TCPCommands.SIP_msg_BUSY+' '+newClient.toTransferString());
				} else { 
					// open "imcoming call" dialog
					conference.setPrivateTalkClient(ip);
					buttonHandling.gui.callFrame.setVisible(true);
				}
				
				
				
				
				
			}
			
			
			else if (firstword.equals(TCPCommands.SIP_msg_BYE)) {
				conference.setCallState(CALL_STATE.NOT_IN_CALL);
				
			} else if (firstword.equals(TCPCommands.SIP_msg_BUSY)) {
				if(conference.getCallState().equals(CALL_STATE.INVITING)){
					//recipiant is busy
					//don't change port, not needed
					conference.setCallState(CALL_STATE.NOT_IN_CALL);
				}
			}else if (firstword.equals(TCPCommands.SIP_msg_OK)) {
				//ip = msg.substring(index+1, msg.indexOf(':'));
				//conference.setPrivateTalkClient(ip);
				conference.clientIsInitiator = true;
				conference.setCallState(CALL_STATE.IN_CALL);
			}
		}
		

	}

	/**
	 * adds a message to send-queue (will eventually be taken care of and sent away by the output thread)
	 * @param msg The message to be added to the send-queue
	 */
	 void send(String msg){
		sendingList.addMsg(msg);
	}
	
	
}

	